//@auther foolbread
//@time 2016-03-09
//@file ApnsHttp2/apns/apns_client.go
package apns

import (
	"ApnsHttp2/config"
	"ApnsHttp2/log"
	"ApnsHttp2/statics"
	"github.com/foolbread/fbcommon/golog"
	apns "github.com/sideshow/apns2"
	"github.com/sideshow/apns2/certificate"

	"sync"
	"time"
)

/*
200
Success
400
Bad request
403
There was an error with the certificate.
405
The request used a bad :method value. Only POST requests are supported.
410
The device token is no longer active for the topic.
413
The notification payload was too large.
429
The server received too many requests for the same device token.
500
Internal server error
503
The server is shutting down and unavailable.
*/

type expectedMessage struct {
	m       *ApnsMessage
	f       func(*ApnsMessage, int)
	success bool
}

func newExpectedMessage(m *ApnsMessage, f func(*ApnsMessage, int)) *expectedMessage {
	em := new(expectedMessage)
	em.m = m
	em.f = f
	em.success = false
	return em
}

type apnsClient struct {
	info   *config.CertificateInfo
	client *apns.Client
	exMap  map[string]*expectedMessage //key-> uuidstr
	r      *sync.Mutex
}

func newApnsClient(i *config.CertificateInfo) *apnsClient {
	ac := new(apnsClient)
	ac.info = i
	ac.exMap = make(map[string]*expectedMessage)
	ac.r = new(sync.Mutex)

	cert, err := certificate.FromP12File(i.FilePath, i.PassWord)
	if err != nil {
		golog.Critical(err)
	}
	if i.IsProduct {
		ac.client = apns.NewClient(cert).Production()
	} else {
		ac.client = apns.NewClient(cert).Development()
	}

	go ac.clearExpectedMsg()

	return ac
}

func (a *apnsClient) pushNotification(m *ApnsMessage, f func(*ApnsMessage, int)) {
	noti := &apns.Notification{}
	noti.DeviceToken = m.M.Token
	noti.Topic = a.info.Topic
	noti.ApnsID = m.Uidstr
	noti.Payload = m.M.DataByte
	noti.Expiration = time.Now().Add(time.Duration(m.M.Expiry) * time.Second)

	var st int = 0
	for i := 0; i < 3; i++ {
		res, err := a.client.Push(noti)
		if err != nil {
			log.LogFile().Errorf("push notification to apns error:%s, sn:%s, token:%s, type:%d", err, m.M.Sn, m.M.Token, m.M.Type)
			continue
		}

		if res != nil {
			st = res.StatusCode
			if !res.Sent() {
				log.LogFile().Errorf("type:%d, sn:%s, token:%s, reason:%s", m.M.Type, m.M.Sn, m.M.Token, res.Reason)
			}
		}

		break
	}

	if st == 0 {
		a.r.Lock()
		a.exMap[m.Uidstr] = newExpectedMessage(m, f)
		a.r.Unlock()
		statics.IncreExcepteMsg(m.M.Type)
		return
	}

	f(m, st)
}

func (a *apnsClient) clearExpectedMsg() {
	ti := time.Tick(30 * time.Second)
	for {
		<-ti
		var expet []*expectedMessage

		a.r.Lock()
		for _, v := range a.exMap {
			expet = append(expet, v)
		}
		a.r.Unlock()

		if len(expet) == 0 {
			continue
		}

		for _, v := range expet {
			r := a.pushExpectedMsg(v.m)
			if r != 0 {
				v.success = true
				v.f(v.m, r)
			}
		}

		a.r.Lock()
		for _, v := range expet {
			if v.success {
				statics.DecreExcepteMsg(v.m.M.Type)
				delete(a.exMap, v.m.Uidstr)
			}
		}
		a.r.Unlock()
	}
}

func (a *apnsClient) pushExpectedMsg(m *ApnsMessage) int {
	noti := &apns.Notification{}
	noti.DeviceToken = m.M.Token
	noti.Topic = a.info.Topic
	noti.ApnsID = m.Uidstr
	noti.Payload = m.M.DataByte
	noti.Expiration = time.Now().Add(time.Duration(m.M.Expiry) * time.Second)

	var st int = 0
	for i := 0; i < 3; i++ {
		res, err := a.client.Push(noti)
		if err != nil {
			log.LogFile().Errorf("push notification to apns error:%s, sn:%s, token:%s, type:%d", err, m.M.Sn, m.M.Token, m.M.Type)
			continue
		}

		if res != nil {
			st = res.StatusCode
			if !res.Sent() {
				log.LogFile().Errorf("type:%d, sn:%s, token:%s, reason:%s", m.M.Type, m.M.Sn, m.M.Token, res.Reason)
			}
		}

		break
	}

	return st
}
